/* 
 * AndFollowerStat
 * 
 * Copyright (C) 2009  Daniel Czerwonk <d.czerwonk@googlemail.com>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.dan_nrw.android.followerstat.dao.folllowers;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.google.inject.Inject;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import de.dan_nrw.android.followerstat.Follower;


/**
 * @author Daniel Czerwonk
 *
 */
public final class TwitterFollowerDAO implements ITwitterFollowerDAO {

	private static final int MAX_RETRIES = 10;
	private final UsernamePasswordCredentials credentials;

	
	/**
     * Creates a new instance of TwitterFollowerDAO.
     */
	@Inject
	TwitterFollowerDAO(UsernamePasswordCredentials credentials) {
	    super();
	    
	    this.credentials = credentials;
    }

    
	/**
	 * Creates a new instance of TwitterFollowerDAO
	 * @return
	 */
	public static ITwitterFollowerDAO createInstance(UsernamePasswordCredentials credentials) {
		return new TwitterFollowerDAO(credentials);
	}
	
	/* (non-Javadoc)
     * @see de.dan_nrw.android.followerstat.dao.ITwitterFollowerDAO#getFollowerCount(java.lang.String)
     */
    @Override
    public long getFollowerCount(String userName) throws TwitterUserAccountNotFoundException, TwitterRequestException {
        try {
        	String json = this.sendRequest(URI.create(String.format("http://twitter.com/users/show/%s.json", userName)),
        								   new StringResponseHandler());
	        
	        JSONObject jsonObject = new JSONObject(json);
	        
	        return jsonObject.getLong("followers_count");
        }
        catch (ClientProtocolException ex) {
        	throw new TwitterRequestException(ex);
        }
        catch (SecurityException ex) {
        	throw new TwitterAuthException(ex.getCause());
        }
        catch (IOException ex) {
        	if (ex.getMessage().equals("404")) {
        		throw new TwitterUserAccountNotFoundException();
        	}
        	
	        throw new TwitterRequestException(ex);
        }
        catch (JSONException ex) {
        	throw new TwitterResponseParsingException(ex);
        }
    }
	
	/* (non-Javadoc)
     * @see de.dan_nrw.android.followerstat.dao.folllowers.ITwitterFollowerDAO#getFollowers(java.lang.String)
     */
    @Override
    public Follower[] getFollowers(String userName) throws TwitterUserAccountNotFoundException, TwitterRequestException {
        try {
        	String json = this.sendRequest(URI.create(String.format("http://twitter.com/statuses/followers/%s.json", userName)),
        								   new StringResponseHandler());
        	
        	JSONArray jsonArray = new JSONArray(json);
        	
        	List<Follower> followers = new ArrayList<Follower>();
        	
        	for (int i = 0; i < jsonArray.length(); i++) {
        		JSONObject jsonObject = jsonArray.getJSONObject(i);
        		
        		followers.add(new Follower(jsonObject.getString("screen_name"), 
        								   URI.create(jsonObject.getString("profile_image_url"))));
        	}
        	
        	return followers.toArray(new Follower[followers.size()]);
        }
        catch (ClientProtocolException ex) {
        	throw new TwitterRequestException(ex);
        }
        catch (SecurityException ex) {
        	throw new TwitterAuthException(ex.getCause());
        }
        catch (IOException ex) {
        	if (ex.getMessage().equals("404")) {
        		throw new TwitterUserAccountNotFoundException();
        	}
        	
	        throw new TwitterRequestException(ex);
        }
        catch (JSONException ex) {
        	throw new TwitterResponseParsingException(ex);
        }
    }
    
	/* (non-Javadoc)
     * @see de.dan_nrw.android.followerstat.dao.folllowers.ITwitterFollowerDAO#getFollowerImage(de.dan_nrw.android.followerstat.Follower)
     */
    @Override
    public Bitmap getFollowerImage(Follower follower) throws TwitterRequestException {
	    try {
	        return sendRequest(follower.getImageUri(), new BitmapResponseHandler());
        }
        catch (ClientProtocolException ex) {
        	throw new TwitterRequestException(ex);
        }
        catch (IOException ex) {
        	throw new TwitterRequestException(ex);
        }
    }

	private <T> T sendRequest(URI uri, ResponseHandler<T> responseHandler) throws ClientProtocolException, IOException {
		DefaultHttpClient client = new DefaultHttpClient();
		
		client.getCredentialsProvider().setCredentials(AuthScope.ANY, this.credentials);
		client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(MAX_RETRIES, true));
		
		HttpGet request = new HttpGet(uri);
		
		return client.execute(request, responseHandler);
	}
    
	private static class BitmapResponseHandler implements ResponseHandler<Bitmap> {

		/* (non-Javadoc)
         * @see org.apache.http.client.ResponseHandler#handleResponse(org.apache.http.HttpResponse)
         */
        @Override
        public Bitmap handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
        	int statusCode = response.getStatusLine().getStatusCode();
        	
        	if (statusCode >= 300) {
	        	if (statusCode == 401) {
	        		throw new SecurityException();
	        	}
	        	
	        	throw new IOException(Integer.toString(response.getStatusLine().getStatusCode()));
	        }
        	
        	return BitmapFactory.decodeStream(new BufferedInputStream(response.getEntity().getContent()));
        }
	}
	
	private static class StringResponseHandler implements ResponseHandler<String> {

		/* (non-Javadoc)
         * @see org.apache.http.client.ResponseHandler#handleResponse(org.apache.http.HttpResponse)
         */
        @Override
        public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
	        int statusCode = response.getStatusLine().getStatusCode();
        	
        	if (statusCode >= 300) {
	        	if (statusCode == 401) {
	        		throw new SecurityException();
	        	}
	        	
	        	throw new IOException(Integer.toString(response.getStatusLine().getStatusCode()));
	        }
	        
	        BufferedReader reader = null;
	        
	        try {
	        	reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
	        	
	        	char[] buffer = new char[1024];
	        	int charsRead = 0;  
	        	StringBuilder stringBuilder = new StringBuilder();
	        	
	        	while ((charsRead = reader.read(buffer)) > 0) {
	        		stringBuilder.append(buffer, 0, charsRead);
	        	}
	        	
	        	return stringBuilder.toString();
	        }
	        finally {
	        	if (reader != null) {
	        		try {
	        			reader.close();	
	        		}
	        		catch (IOException ex) {
	        			// exception could hide exception thrown in try block
	        		}
	        	}
	        }
        }
	}
}